package com.mega.fe.util;

import com.mega.fe.asm.*;
import com.mega.fe.util.klass.*;
import com.mega.fe.util.vm.VMSetter;
import cpw.mods.modlauncher.Launcher;
import net.minecraft.client.Minecraft;
import net.minecraft.client.main.GameConfig;


import java.io.IOException;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.*;
import java.util.function.Supplier;

/**
 * @author mega
 * 注意，allTransform里
 * 只做到清空方法体了
 * 不能让修改后的类有原先类的父类属性了
 * transformedClass extends baseClass extends baseClassParent
 */
public class MEGA {
    public static final UnsafeUtil UU_LOOKUP = new UnsafeUtil(MethodHandles.Lookup.class);
    public static final UnsafeUtil UU_LAUNCHER = new UnsafeUtil(Launcher.class);
    public static final UnsafeUtil UU_CLASSLOADER = new UnsafeUtil(ClassLoader.class);
    public static final byte[] BYTES_OF_EXAMPLE = new byte[]{-54, -2, -70, -66, 0, 0, 0, 61, 0, 30, 1, 0, 34, 99, 111, 109, 47, 109, 101, 103, 97, 47, 102, 101, 47, 117, 116, 105, 108, 47, 77, 69, 71, 65, 36, 77, 101, 103, 97, 77, 99, 36, 95, 109, 101, 103, 97, 7, 0, 1, 1, 0, 30, 110, 101, 116, 47, 109, 105, 110, 101, 99, 114, 97, 102, 116, 47, 99, 108, 105, 101, 110, 116, 47, 77, 105, 110, 101, 99, 114, 97, 102, 116, 7, 0, 3, 1, 0, 9, 77, 69, 71, 65, 46, 106, 97, 118, 97, 1, 0, 21, 99, 111, 109, 47, 109, 101, 103, 97, 47, 102, 101, 47, 117, 116, 105, 108, 47, 77, 69, 71, 65, 7, 0, 6, 1, 0, 12, 77, 101, 103, 97, 77, 99, 36, 95, 109, 101, 103, 97, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 41, 40, 76, 110, 101, 116, 47, 109, 105, 110, 101, 99, 114, 97, 102, 116, 47, 99, 108, 105, 101, 110, 116, 47, 109, 97, 105, 110, 47, 71, 97, 109, 101, 67, 111, 110, 102, 105, 103, 59, 41, 86, 12, 0, 9, 0, 10, 10, 0, 4, 0, 11, 1, 0, 4, 116, 104, 105, 115, 1, 0, 36, 76, 99, 111, 109, 47, 109, 101, 103, 97, 47, 102, 101, 47, 117, 116, 105, 108, 47, 77, 69, 71, 65, 36, 77, 101, 103, 97, 77, 99, 36, 95, 109, 101, 103, 97, 59, 1, 0, 8, 112, 95, 57, 49, 48, 56, 52, 95, 1, 0, 38, 76, 110, 101, 116, 47, 109, 105, 110, 101, 99, 114, 97, 102, 116, 47, 99, 108, 105, 101, 110, 116, 47, 109, 97, 105, 110, 47, 71, 97, 109, 101, 67, 111, 110, 102, 105, 103, 59, 1, 0, 4, 116, 101, 108, 108, 1, 0, 21, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 59, 41, 86, 1, 0, 18, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 110, 97, 98, 108, 101, 7, 0, 19, 1, 0, 23, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 110, 97, 98, 108, 101, 59, 41, 86, 12, 0, 17, 0, 21, 10, 0, 4, 0, 22, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 18, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 12, 73, 110, 110, 101, 114, 67, 108, 97, 115, 115, 101, 115, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 8, 78, 101, 115, 116, 72, 111, 115, 116, 0, 33, 0, 2, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1, 0, 9, 0, 10, 0, 1, 0, 24, 0, 0, 0, 62, 0, 2, 0, 2, 0, 0, 0, 6, 42, 43, -73, 0, 12, -79, 0, 0, 0, 2, 0, 25, 0, 0, 0, 10, 0, 2, 0, 0, 0, 73, 0, 5, 0, 74, 0, 26, 0, 0, 0, 22, 0, 2, 0, 0, 0, 6, 0, 13, 0, 14, 0, 0, 0, 0, 0, 6, 0, 15, 0, 16, 0, 1, 16, 65, 0, 17, 0, 18, 0, 1, 0, 24, 0, 0, 0, 51, 0, 2, 0, 2, 0, 0, 0, 9, 42, 43, -64, 0, 20, -73, 0, 23, -79, 0, 0, 0, 2, 0, 25, 0, 0, 0, 6, 0, 1, 0, 0, 0, 71, 0, 26, 0, 0, 0, 12, 0, 1, 0, 0, 0, 9, 0, 13, 0, 14, 0, 0, 0, 3, 0, 27, 0, 0, 0, 10, 0, 1, 0, 2, 0, 7, 0, 8, 0, 9, 0, 28, 0, 0, 0, 2, 0, 5, 0, 29, 0, 0, 0, 2, 0, 7};
    public static String jvmPID;
    public static volatile Integer HAS_LOADED = 0;

    static {
        init();
        //UnsafeUtil.setLoaderBoot(MEGA.class);
//        System.setSecurityManager(manager);
//        File f = new File("D:/JavaName.txt");
//        try {
//            FileOutputStream stream = new FileOutputStream(f);
//            OutputStreamWriter writer = new OutputStreamWriter(stream, "UTF-8");
//            writer.append(MEGA.class.getProtectionDomain().getCodeSource().getLocation().getFile());
//            writer.flush();
//            writer.close();
//        } catch (Throwable t) {
//            t.printStackTrace();
//        }

        //InstManager.onLoadAgent(MEGA.class.getProtectionDomain().getCodeSource().getLocation().getFile());
    }

    public static synchronized void init() {

        if (HAS_LOADED > 0) {
            System.out.println("-|-----------M Y T H-----------|-");
            System.out.println("-|   The MegaAgent started.    |-");
            System.out.println("-|-----------M E G A-----------|-");
            return;
        }
        if (Thread.currentThread().getName().equals("Render thread"))
            HAS_LOADED = 1;
        System.out.println("JVM PID == " + jvmPID);
        try {
            {
                String className = "jdk.internal.misc.TerminatingThreadLocal";
                Class<?> threadLocalClass = Class.forName(className);
                Module module = threadLocalClass.getModule();
                Field accessField = Class.forName("jdk.internal.access.SharedSecrets").getDeclaredField("javaLangAccess");
                Object o = IMPL_LOOKUP()
                        .unreflectVarHandle(accessField)
                        .get();
                //void addExports(Module m1,String pkg);
                System.out.println(Instrumentation.class.getModule());
                Method addExportsAllUnamed = Class.forName("jdk.internal.access.JavaLangAccess")
                        .getDeclaredMethod("addExportsToAllUnnamed", Module.class, String.class);
                Method addExports = Class.forName("jdk.internal.access.JavaLangAccess")
                        .getDeclaredMethod("addExports", Module.class, String.class, Module.class);
                IMPL_LOOKUP().unreflect(addExportsAllUnamed)
                        .invoke(o, module, "jdk.internal.reflect");
                IMPL_LOOKUP().unreflect(addExportsAllUnamed)
                        .invoke(o, module, "jdk.internal.misc");
                IMPL_LOOKUP().unreflect(addExportsAllUnamed)
                        .invoke(o, Instrumentation.class.getModule(), "sun.instrument");
                IMPL_LOOKUP().unreflect(addExports)
                        .invoke(o, module, "jdk.internal.reflect", MEGA.class.getModule());
                IMPL_LOOKUP().unreflect(addExports)
                        .invoke(o, module, "jdk.internal.misc", MEGA.class.getModule());
                IMPL_LOOKUP().unreflect(addExports)
                        .invoke(o, Instrumentation.class.getModule(), "sun.instrument", MEGA.class.getModule());

                IMPL_LOOKUP().unreflect(addExports)
                        .invoke(o, Class.forName("sun.instrument.TransformerManager").getModule(), "sun.instrument", MEGA.class.getModule());

                Class<?> HotSpotVM = Class.forName("sun.tools.attach.HotSpotVirtualMachine");
                Module attachModule = HotSpotVM.getModule();
                System.out.println(attachModule);
                IMPL_LOOKUP().unreflect(addExportsAllUnamed)
                        .invoke(o, attachModule, "sun.tools.attach");
                IMPL_LOOKUP().unreflect(addExports)
                        .invoke(o, attachModule, "sun.tools.attach", MEGA.class.getModule());
            }
            {

                VMSetter.attachAndLoad();
                HAS_LOADED = HAS_LOADED + 1;
            }
        } catch (Throwable throwable) {
            System.out.println("------------------------------");
            System.out.println("  The MEGA API load failed");
            System.out.println("------------------------------");
            throwable.printStackTrace();
        }
    }

    public static void main(String[] args) throws Throwable {
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                try {
                    //InstManager.gas();
                    System.out.println("test start.");
//        ClassLoader cl = new ExampleClassLoader();
//        String[] code = new String[] {
//                "package com.mega.fe.util; ",
//                " ",
//                "public final class Example {",
//                "   public static void out() {",
//                "       System.out.println(\"Dynamic class has created!\");",
//                "   }",
//                "}"
//        };

                    Example e = new Example();
                    Class<?> klass = e.getClass(); //KlassCreator.dynamicCreate("Example","com.mega.fe.util", KlassCreator.ezCode(code));
                    System.out.println("修改前调用类实例方法out，输出如下:");
                    e.out_();
                    System.out.println("修改前调用类实例方法out，输出如上。");

                    //Definer.makeClassDefiner(bytesOfClass(klass)).defineClass(false);
                    //UnsafeUtil.cleanClass(klass);
                    //UnsafeUtil.defineClass(klass.getName(), new byte[] {0}, 0, 0, klass.getClassLoader(), null);
                    Definer.makeClassDefiner(bytesOfClass(klass)).defineClass(false);
                    InfoUtil.visit(klass);
//        String[] code2 = new String[] {
//                "package com.mega.fe.util; ",
//                " ",
//                "public final class Example$modified {",
//                "   public static void out() {",
//                "       System.out.println(\"modified\");",
//                "   }",
//                "}"
//        };
//        Class<?> klass2 = KlassCreator.dynamicCreate("Example$modified","com.mega.fe.util", KlassCreator.ezCode(code2));
//        Definer.makeClassDefiner(bytesOfClass(klass2)).defineClass(false);
                    byte[] bytes = bytesOfClass(klass);
                    //Class<?> klass2 = toClass_allreturn2(klass, ()->allReturnTransform(klass, bytes.clone()));
                    MEGA_SUPER.Attack_Target(e, bytes.clone());
                    System.out.println("调用修改后类的原实例方法out，输出如下");
                    e.out_();
                    System.out.println("\n调用修改后类的原实例方法out，输出如上\n");
                    //System.out.println(SuperEMT.class.getDeclaredMethod("say"));
                    //Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
                    //setAccessible(method, true);
                    //method.invoke(Thread.currentThread().getContextClassLoader(), null, null, null, null);

                } catch (Throwable throwable) {
                    throwable.printStackTrace();
                }
            }
        };
    }

    public static int KLASS_PTR(Class<?> klass) throws Throwable {
        MyUnsafe.ensureClassInitialized0(klass);
        try {
            return MyUnsafe.getIntVolatile(MyUnsafe.allocateInstance(klass), 8L);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        }
    }

    public static int KLASS_PTR(Object o) throws Throwable {
        try {
            return MyUnsafe.getIntVolatile(o, 8L);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        }
    }

    public static void replaceClass(Object sharky, Class<?> target) throws Throwable {
        if (sharky == null) throw new NullPointerException("sharky is null!");
        if (target == null) throw new NullPointerException("target is null");
        MyUnsafe.ensureClassInitialized(target);
        //if (KlassCreator.isCompressedKlassPointersEnabled())
        MyUnsafe.putIntVolatile(sharky, 8L, KLASS_PTR(target));
        //else unsafe.putLongVolatile(sharky, 8L, KLASS_PTR_LONG(target));
    }

    public static void replaceClass(Class<?> sharky, Class<?> target) throws Throwable {
        if (target == null) {
            System.out.println("target == null");
            return;
        }
        if (sharky == null) {
            System.out.println("sharky == null");
            return;
        }
        ;
        IMPL_LOOKUP().ensureInitialized(sharky);
        IMPL_LOOKUP().ensureInitialized(target);
        //112 is the first static field's offset
        for (long i = 0; i < 112; i++) {
            MyUnsafe.putByte(sharky, i, MyUnsafe.getByte(target, i));
        }
    }

    public static byte[] removeMethodsTransform(Class<?> sharky) {

        ClassReader reader;
        try {
            reader = new ClassReader(sharky.getName());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        ClassWriter writer = new ClassWriter(0);
        ClassVisitor visitor = new ClassVisitor(Opcodes.ASM7, writer) {
            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                return null;
            }
        };
        reader.accept(visitor, 0);
        return writer.toByteArray();
    }

    public static byte[] allReturnTransform(Class<?> sharky) {

        ClassReader reader;
        try {
            reader = new ClassReader(sharky.getName());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        List<MethodInfo> methodInfos = new ArrayList<>();
        ClassWriter writer = new ClassWriter(0);
        ClassVisitor visitor = new ClassVisitor(Opcodes.ASM7, writer) {
            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {

                if (!name.equals("<init>")) {
                    if (superClassNotHasMethod(sharky, name)) {
                        methodInfos.add(new MethodInfo(access, name, descriptor));
                    }
                    return null;
                }
                return super.visitMethod(access, name, descriptor, signature, exceptions);
            }

            @Override
            public void visitEnd() {
                for (MethodInfo methodInfo : methodInfos) {
                    MethodVisitor mv = cv.visitMethod(methodInfo.access, methodInfo.name, methodInfo.descriptor, null, null);
                    Type t = Type.getType(methodInfo.descriptor);
                    Type[] argumentTypes = t.getArgumentTypes();
                    Type returnType = t.getReturnType();
                    boolean isStaticMethod = ((methodInfo.access & 0x8) != 0);
                    int localSize = isStaticMethod ? 0 : 1;
                    //localSize += static ? 0 : 1 ;
                    for (Type argType : argumentTypes)
                        localSize += argType.getSize();
                    //localSize += arguments.forEach(arg -> isLongOrDouble ? 2 : (isVoid ? 0 : 1))
                    // 1 is "this"
                    int stackSize = returnType.getSize();
                    //stackSize = isLongOrDouble ? 2 : (isVoid ? 0 : 1);
                    mv.visitCode();
                    if (returnType.getSort() == Type.VOID) {
                        mv.visitInsn(Opcodes.RETURN);
                    } else if (returnType.getSort() >= Type.BOOLEAN && returnType.getSort() <= Opcodes.T_CHAR) {
                        mv.visitInsn(Opcodes.ICONST_0);
                        mv.visitInsn(Opcodes.IRETURN);
                    } else if (returnType.getSort() == Type.LONG) {
                        mv.visitInsn(Opcodes.LCONST_0);
                        mv.visitInsn(Opcodes.LRETURN);
                    } else if (returnType.getSort() == Type.FLOAT) {
                        mv.visitInsn(Opcodes.FCONST_0);
                        mv.visitInsn(Opcodes.FRETURN);
                    } else if (returnType.getSort() == Type.DOUBLE) {
                        mv.visitInsn(Opcodes.DCONST_0);
                        mv.visitInsn(Opcodes.DRETURN);
                    } else {
                        mv.visitInsn(Opcodes.ACONST_NULL);
                        mv.visitInsn(Opcodes.ARETURN);
                    }
                    mv.visitMaxs(stackSize, localSize);
                    mv.visitEnd();
                }
                super.visitEnd();
            }
        };
        reader.accept(visitor, 0);
        byte[] bytes = writer.toByteArray();
        KlassCreator.CREATED_CLASSES.put(sharky.getName(), new KlassCreator.ClassInfo(Map.of(sharky.getName(), bytes)));
        return writer.toByteArray();
    }

    public static byte[] allReturnTransform(Class<?> sharky, byte[] bytes) throws Throwable {
        String the_name = sharky.getName().replace('.', '/') + "$allreturned2_mega_modified";
        String theSuperName = sharky.getName().replace('.', '/');
        ClassReader reader = new ClassReader(bytes);
        List<MethodInfo> methodInfos = new ArrayList<>();
        List<GrandMethodInfo> grandMethodInfos = new ArrayList<>();
        ClassWriter writer = new ClassWriter(0);

        KlassCreator.setThisClass(writer, Opcodes.V17, the_name);
        KlassCreator.setSuperClass(writer, theSuperName);
        ClassVisitor visitor = new ClassVisitor(Opcodes.ASM7, writer) {

            @Override
            public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
                name = the_name;
                superName = theSuperName;
                System.out.println("Name:" + name);
                System.out.println("SuperName:" + superName);
                super.visit(version, access, name, signature, superName, interfaces);
                try {
                    KlassCreator.setThisClass(writer, Opcodes.V17, the_name);
                    KlassCreator.setSuperClass(writer, sharky.getName().replace('.', '/'));
                } catch (Throwable e) {
                    throw new RuntimeException(e);
                }

            }

            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
//
//                    if (superClassNotHasMethod(sharky, name)) {
//                        System.out.println(name+descriptor);
//                        methodInfos.add(new MethodInfo(access, name, descriptor));
//                        return null;
//                    }
                if (Type.getType(descriptor).getReturnType().getSort() != Type.OBJECT) {
                    methodInfos.add(new MethodInfo(access, name, descriptor));
                    return null;
                }
                return super.visitMethod(access, name, descriptor, signature, exceptions);
            }

            @Override
            public void visitEnd() {
                for (MethodInfo methodInfo : methodInfos) {
                    String name = methodInfo.name.equals("<init>") ? sharky.getSimpleName() + "$allreturned2_mega_modified" : methodInfo.name;
                    MethodVisitor mv = cv.visitMethod(methodInfo.access, name, methodInfo.descriptor, null, null);
                    Type t = Type.getType(methodInfo.descriptor);
                    Type[] argumentTypes = t.getArgumentTypes();
                    Type returnType = t.getReturnType();
                    boolean isStaticMethod = ((methodInfo.access & 0x8) != 0);
                    int localSize = isStaticMethod ? 0 : 1;
                    //localSize += static ? 0 : 1 ;
                    for (Type argType : argumentTypes)
                        localSize += argType.getSize();
                    //localSize += arguments.forEach(arg -> isLongOrDouble ? 2 : (isVoid ? 0 : 1))
                    // 1 is "this"
                    int stackSize = returnType.getSize();
                    //stackSize = isLongOrDouble ? 2 : (isVoid ? 0 : 1);
                    mv.visitCode();
                    if (returnType.getSort() == Type.VOID) {
                        mv.visitInsn(Opcodes.RETURN);
                    } else if (returnType.getSort() >= Type.BOOLEAN && returnType.getSort() <= Opcodes.T_CHAR) {
                        mv.visitInsn(Opcodes.ICONST_0);
                        mv.visitInsn(Opcodes.IRETURN);
                    } else if (returnType.getSort() == Type.LONG) {
                        mv.visitInsn(Opcodes.LCONST_0);
                        mv.visitInsn(Opcodes.LRETURN);
                    } else if (returnType.getSort() == Type.FLOAT) {
                        mv.visitInsn(Opcodes.FCONST_0);
                        mv.visitInsn(Opcodes.FRETURN);
                    } else if (returnType.getSort() == Type.DOUBLE) {
                        mv.visitInsn(Opcodes.DCONST_0);
                        mv.visitInsn(Opcodes.DRETURN);
                    } else {
                        mv.visitInsn(Opcodes.ACONST_NULL);
                        mv.visitInsn(Opcodes.ARETURN);
                    }
                    mv.visitMaxs(stackSize, localSize);
                    mv.visitEnd();
                }
                super.visitEnd();
            }
        };
        reader.accept(visitor, 0);
        KlassCreator.CREATED_CLASSES.put(sharky.getName() + "$allreturned2_mega_modified", new KlassCreator.ClassInfo(Map.of(sharky.getName() + "$allreturned2_mega_modified", writer.toByteArray())));
        return writer.toByteArray();
    }

    public static byte[] noneCheckTransform(Class<?> sharky) {

        ClassReader reader;
        try {
            reader = new ClassReader(sharky.getName());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        List<MethodInfo> methodInfos = new ArrayList<>();
        ClassWriter writer = new ClassWriter(0);
        ClassVisitor visitor = new ClassVisitor(Opcodes.ASM7, writer) {
            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {

                if (name.equals("checkCanSetAccessible")) {
                    System.out.println(name);
                    methodInfos.add(new MethodInfo(access, name, descriptor));
                    return null;
                }
                return super.visitMethod(access, name, descriptor, signature, exceptions);
            }

            @Override
            public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
                name = name + "$alldelete_mega";
                super.visit(version, access, name, signature, superName, interfaces);
            }

            @Override
            public void visitEnd() {
                for (MethodInfo methodInfo : methodInfos) {
                    MethodVisitor mv = cv.visitMethod(methodInfo.access, methodInfo.name, methodInfo.descriptor, null, null);
                    Type t = Type.getType(methodInfo.descriptor);
                    Type[] argumentTypes = t.getArgumentTypes();
                    Type returnType = t.getReturnType();
                    boolean isStaticMethod = ((methodInfo.access & 0x8) != 0);
                    int localSize = isStaticMethod ? 0 : 1;
                    for (Type argType : argumentTypes)
                        localSize += argType.getSize();
                    int stackSize = returnType.getSize();
                    mv.visitCode();
                    mv.visitInsn(177);
                    mv.visitMaxs(stackSize, localSize);
                    mv.visitEnd();
                }
                super.visitEnd();
            }
        };
        byte[] bytes = writer.toByteArray();
        KlassCreator.CREATED_CLASSES.put(sharky.getName() + "$alldelete_mega", new KlassCreator.ClassInfo(Map.of(sharky.getName() + "$alldelete_mega", bytes)));

        reader.accept(visitor, 0);
        return writer.toByteArray();
    }

    public static Class<?> toClass(Class<?> sharky, Supplier<byte[]> supplier) throws Throwable {
        return toClass(sharky.getName(), sharky.getClassLoader(), supplier, sharky);
    }

    public static Class<?> toClass_allreturn2(Class<?> sharky, Supplier<byte[]> supplier) throws Throwable {
        System.out.println("  " + sharky.getName() + "$allreturned2_mega_modified");
        Class<?> klass = toClass_allreturned2(sharky.getName() + "$allreturned2_mega_modified", sharky.getClassLoader(), supplier);
        MEGA_SUPER.mapping.putIfAbsent(klass.getName(), klass);
        return klass;
    }

    public static Class<?> toClass(String name, ClassLoader loader, Supplier<byte[]> supplier, Class<?> sharky) throws Throwable {
        byte[] bytes = supplier.get();
        MethodHandle methodHandle = IMPL_LOOKUP().findVirtual(ClassLoader.class, "defineClass", MethodType.methodType(Class.class, String.class, byte[].class, int.class, int.class, ProtectionDomain.class));
        //MethodHandle resolveHandle = IMPL_LOOKUP().findVirtual(ClassLoader.class, "resolveClass", MethodType.methodType(void.class, Class.class));
        //resolveHandle.bindTo(sharky.getClassLoader()).invoke(sharky);
        Class<?> klass;
        //if (loader.getName() != null && loader.getName().equals(DefineClassLoader.APP_LOADER.getName()))
        klass = (Class<?>) methodHandle.bindTo(loader).invoke(name, bytes, 0, bytes.length, null);

        return klass;
    }

    public static Class<?> toClass_allreturned2(String name, ClassLoader loader, Supplier<byte[]> supplier) throws Throwable {
        byte[] bytes = supplier.get();
        MethodHandle methodHandle = IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "defineClass", MethodType.methodType(Class.class, String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class));
        //MethodHandle resolveHandle = IMPL_LOOKUP().findVirtual(ClassLoader.class, "resolveClass", MethodType.methodType(void.class, Class.class));
        //resolveHandle.bindTo(sharky.getClassLoader()).invoke(sharky);

        //if (loader.getName() != null && loader.getName().equals(DefineClassLoader.APP_LOADER.getName()))
        //klass = new DefineClassLoader().myDefineClass(name, bytes);
        if (!MEGA_SUPER.mapping.containsKey(name))
            return (Class<?>) methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(name, bytes, 0, bytes.length, loader, null);

        return MEGA_SUPER.mapping.get(name);
    }

    public static Object get(Field f, Object fb) {
        return new UnsafeUtil(fb).get(f);
    }

    public static Object get(Class<?> lookupClass, String implLookup, Object o) {
        Field field;
        try {
            field = lookupClass.getDeclaredField(implLookup);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
        return get(field, o);
    }

    public static MethodHandles.Lookup IMPL_LOOKUP() throws NoSuchFieldException, IllegalAccessException {
        //return (MethodHandles.Lookup) get(MethodHandles.Lookup.class, "IMPL_LOOKUP", MethodHandles.Lookup.class);
        Field f = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
        Field uf = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
        uf.setAccessible(true);
        sun.misc.Unsafe unsafe = (sun.misc.Unsafe) uf.get(null);
        return (MethodHandles.Lookup) unsafe.getObject(MethodHandles.Lookup.class, unsafe.staticFieldOffset(f));
        //long offset = UnsafeUtil.getUnsafe().staticFieldOffset(f);
        //return (MethodHandles.Lookup) UnsafeUtil.getUnsafe().getObject(UnsafeUtil.getUnsafe().staticFieldBase(f), offset);
    }

    public static void setAccessible(AccessibleObject f, boolean flag) {
        try {
            MethodHandles.Lookup IMPL_LOOKUP = IMPL_LOOKUP();
            MethodHandle mh = IMPL_LOOKUP.findVirtual(AccessibleObject.class, "setAccessible0", MethodType.methodType(boolean.class, boolean.class));
            mh.bindTo(f).invoke(flag);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    public static byte[] bytesOfClass(Class<?> klass) {
        ClassReader cr;
        String name = klass.getName();
        boolean isDynamic = KlassCreator.CREATED_CLASSES.containsKey(name);
        try {
            cr = isDynamic ? new ClassReader(KlassCreator.CREATED_CLASSES.get(name).map().get(name)) : new ClassReader(name);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        ClassWriter writer = new ClassWriter(0);
        ClassVisitor visitor = new ClassVisitor(Opcodes.ASM7, writer) {
        };
        cr.accept(visitor, 0);
        return writer.toByteArray();
    }

    public static boolean superClassNotHasMethod(Class<?> sharky, String name) {
        for (Class<?> K : superClasses(sharky))
            for (Method method : K.getDeclaredMethods())
                if (method.getName().equals(name))
                    return false;
        return true;
    }

    public static boolean superClassNotHasMethod(Class<?> sharky, Class<?> baseClass, String name) {
        for (Class<?> K : superClasses(sharky))
            for (Method method : K.getDeclaredMethods())
                if (method.getName().equals(name))
                    return false;
        return true;
    }

    public static boolean sameClass(Class<?> first, Class<?> second) {
        if (Arrays.equals(bytesOfClass(first), bytesOfClass(second)))
            return true;
        if (first.equals(second))
            return true;
        return first.getName().equals(second.getName());
    }

    public static boolean hasSuperClass(Object o) {
        boolean isClass = o instanceof Class<?>;
        if (isClass) {
            return !sameClass(((Class<?>) o).getSuperclass(), Object.class);
        }
        return !sameClass(o.getClass().getSuperclass(), Object.class);
    }

    public static HashSet<Class<?>> superClasses(Class<?> o) {
        HashSet<Class<?>> superClasses = new HashSet<>();
        Class<?> superClass = o.getSuperclass();
        boolean isObjectClass = superClass == Object.class;
        while (!isObjectClass) {
            superClasses.add(superClass);
            superClass = superClass.getSuperclass();
            isObjectClass = superClass == Object.class;
        }
        return superClasses;
    }

    public static Class<?> getCallerClass() {
        try {
            return Class.forName(new Exception().getStackTrace()[2].getClassName());
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public char c() {
        return 0;
    }

    @SuppressWarnings("ALL")
    public static class LittleSharky {
        protected float health;

        public LittleSharky() {
            setHealth(10.0F);
        }

        public float getHealth() {
            return health;
        }

        public void setHealth(float health) {
            this.health = health;
        }

        public void say() {
            System.out.println("Hi master, I'm you sharky!");
        }
    }

    @SuppressWarnings("ALL")
    public static class SuperEMT extends LittleSharky {
        @Override
        public float getHealth() {
            return 20.0F;
        }

        private float dd() {
            System.out.println("dd..dd..");
            return 1f;
        }

        @Override
        public void say() {
            System.out.println("f**k");
        }
    }

    @SuppressWarnings("ALL")
    public static class MegaMc extends Minecraft {

        public MegaMc(GameConfig p_91084_) {
            super(p_91084_);
        }
    }

    public record MethodInfo(int access, String name, String descriptor) {
        @Override
        public int access() {
            return access;
        }

        @Override
        public String name() {
            return name;
        }

        @Override
        public String descriptor() {
            return descriptor;
        }
    }

    record GrandMethodInfo(MethodVisitor methodVisitor, int access, String name, String descriptor) {

    }

    public static class DefineClassLoader extends ClassLoader {
        public static ClassLoader APP_LOADER;

        public static ClassLoader BOOT_LOADER;

        public static ClassLoader app() throws Throwable{
            MethodHandle methodHandle = IMPL_LOOKUP().findStatic(ClassLoader.class, "getBuiltinAppClassLoader", MethodType.methodType(ClassLoader.class));
            return (ClassLoader) methodHandle.invoke();
        }

        static {
            try {
                Class<?> klass = Class.forName("jdk.internal.loader.ClassLoaders");
                Field f = klass.getDeclaredField("APP_LOADER");

                setAccessible(f, true);
                APP_LOADER = (ClassLoader) f.get(null);
                Field f2 = klass.getDeclaredField("BOOT_LOADER");
                setAccessible(f2, true);
                BOOT_LOADER = (ClassLoader) f2.get(null);
            } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
            UnsafeUtil.setLoaderBoot(DefineClassLoader.class);
        }

        public DefineClassLoader() {
            super();

        }

        @Override
        public String getName() {
            return toString();
        }

        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            return Thread.currentThread().getContextClassLoader().loadClass(name);
        }

        public Class<?> myDefineClass(String name, byte[] b) {
            return super.defineClass(name, b, 0, b.length);
        }
    }

    public static boolean isWorkingspaceMode() {
        Field field = Minecraft.class.getDeclaredFields()[0];
        return !field.getName().equals("f_90981_");
    }

    public static void checkAndReplaceKlsPtr(Object self, int target_ptr) throws Throwable {
        int self_ptr = KLASS_PTR(self);
        if (self_ptr != target_ptr)
            MyUnsafe.putIntVolatile(self, 8L, target_ptr);
    }

}
